package dmxGroupControl.listeners;
import java.util.ArrayList;
import java.util.stream.Collectors;

import javax.sound.midi.*;

import dmxGroupControl.StatusCode;
import org.jdom2.Element;

import dmxGroupControl.Group;
import dmxGroupControl.LightController;
import dmxGroupControl.Universe;

public class MIDIListener extends Listener implements Receiver
{
    private MidiDevice listeningDevice;
    
	public MIDIListener(String id, String midiDeviceName)
	{
		super(id);
		MidiDevice device;
		MidiDevice.Info[] infos = MidiSystem.getMidiDeviceInfo();
		System.out.println("Seeking MIDI device " + midiDeviceName);
		
		for(MidiDevice.Info info : infos)
		{
			if(info.getName().equals(midiDeviceName))
			{
				try
				{
					device = MidiSystem.getMidiDevice(info);
					System.out.println("Found MIDI device [" + midiDeviceName + "]");
					System.out.println("\tDevice has " + device.getMaxTransmitters() + " transmitter slots");
					if(device.getMaxTransmitters() != 0)
					{
						System.out.println("\tUsing this device");
						listeningDevice = device;
						listeningDevice.open();
						listeningDevice.getTransmitter().setReceiver(this);
                        UpdateStatus(StatusCode.NOTHING_RECEIVED);
						break;
					}
					else
					{
						System.out.println("\tIgnoring this device");
					}
					
				}
				catch(MidiUnavailableException e) 
				{
                    UpdateStatus(e);
                    e.printStackTrace();
				}
			}
			else
			{
				System.out.println("Ignoring MIDI device [" + info.getName() + "]");
			}
		}
        if(listeningDevice == null)
        {
            UpdateStatus("Device [" + midiDeviceName + "] not found");
        }
	}
	
	

	@Override
	public void close()
	{
		if(listeningDevice != null)
		{
			listeningDevice.close();
		}
	}
	
	public void ParseMappings(Universe universe, int startIndex, Element xmlEntry) 
    {   
	    super.ParseMappings(startIndex);
        
        for(Element element : xmlEntry.getChildren("mapping"))
        {
            int cc = Integer.parseInt(element.getAttributeValue("address"));
            String channel = element.getAttributeValue("channel");
            Mapping mapping = new MidiMessageMapping(universe, cc, channel);
            mappings.Add(mapping);
        }
    }

	@Override
	public void send(MidiMessage message, long timeStamp)
	{
	    if(!GetActive())
	    {
	        return;
	    }
	    if(message instanceof ShortMessage)
	    {
		    if(mappings.HasMatchFor(message))
		    {
		    	Mapping mapping = mappings.GetMatchFor(message);
		    	String attribute = mapping.GetAttribute();
		    	float value = mapping.GetValue(message);
		    	int groupInt = mapping.GetGroup(message, fixtureStartIndex);
		    	if(groupInt < mapping.GetUniverse().GetGroups().size())
		    	{
		    		Group group = mapping.GetUniverse().GetGroups().get(groupInt);
		    		group.Set(attribute, value);
		    	}
		    }
	    }
	}
	
	@Override
	public String toString()
	{
		return "MIDI Listener - " + GetAddress();
	}

	@Override
	public String GetType()
	{
		return "midi";
	}

	@Override
	public String GetAddress()
	{
		if(listeningDevice == null)
		{
			return "";
		}
		else
		{
			return listeningDevice.getDeviceInfo().getName();
		}
	}

	public static ArrayList<String> GetOpenDevices()
	{
		ArrayList<String> devices = new ArrayList<>();
		
		MidiDevice device;
		MidiDevice.Info[] infos = MidiSystem.getMidiDeviceInfo();
		
		for(MidiDevice.Info info : infos)
		{
			try
			{
				device = MidiSystem.getMidiDevice(info);
				if(device.getMaxTransmitters() != 0)
				{
					devices.add(device.getDeviceInfo().getName());
					break;
				}
				
			}
			catch(MidiUnavailableException e) 
			{
				e.printStackTrace();
			}
		}

        devices.addAll(
            LightController.GetInstance().GetListeners().stream()
                .filter(
                listener ->
                    (listener instanceof MIDIListener
                        && !listener.GetAddress().isEmpty())
                )
                .map(Listener::GetAddress)
                .collect(Collectors.toList())
        );
		
		return devices;
	}
	
}